/*
 * Copyright (C) 2006-2010 Alfresco Software Limited.
 *
 * This file is part of Alfresco
 *
 * Alfresco is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Alfresco is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 */

package org.alfresco.jlan.util;

/**
 *  The data packing class is a static class that is used to pack and unpack
 *  basic data types to/from network byte order and Intel byte order.
 *
 * @author gkspencer
 */
public final class DataPacker {

  //  Flag to indicate the byte order of the platform that we are currently
  //  running on.

  private static boolean bigEndian = false;

  /**
   * Return the current endian setting.
   *
   * @return    true if the system is big endian, else false.
   */
  public final static boolean isBigEndian() {
    return bigEndian;
  }

  /**
   * Unpack a null terminated data string from the data buffer.
   *
   * @param typ      Data type, as specified by SMBDataType.
   * @param bytarray Byte array to unpack the string value from.
   * @param pos      Offset to start unpacking the string value.
   * @param maxlen   Maximum length of data to be searched for a null character.
   * @param uni			 String is Unicode if true, else ASCII
   * @return         String, else null if the terminating null character was
   *                 not found.
   */
  public final static String getDataString(char typ, byte[] bytarray, int pos, int maxlen, boolean uni) {

    //  Check if the data string has the required data type

    if (bytarray[pos++] == (byte) typ) {

      //  Extract the null terminated string

			if ( uni == true)
				return getUnicodeString(bytarray, wordAlign(pos), maxlen/2);
			else
      	return getString(bytarray, pos, maxlen - 1);
    }

    //  Invalid data type

    return null;
  }

  /**
   * Unpack a 32-bit integer.
   *
   * @param buf  Byte buffer containing the integer to be unpacked.
   * @param pos  Position within the buffer that the integer is stored.
   * @return     The unpacked 32-bit integer value.
   * @exception  java.lang.IndexOutOfBoundsException   If there is not enough data in the buffer.
   */
  public final static int getInt(byte[] buf, int pos) throws java.lang.IndexOutOfBoundsException {

    //  Check if the byte array is long enough

    if (buf.length < pos + 3)
      throw new java.lang.IndexOutOfBoundsException();

    //  Unpack the 32-bit value

    int i1 = (int) buf[pos] & 0xFF;
    int i2 = (int) buf[pos + 1] & 0xFF;
    int i3 = (int) buf[pos + 2] & 0xFF;
    int i4 = (int) buf[pos + 3] & 0xFF;

    int iVal = (i1 << 24) + (i2 << 16) + (i3 << 8) + i4;

    //  Return the unpacked value

    return iVal;
  }

  /**
   * Unpack a 32-bit integer that is stored in Intel format.
   *
   * @param bytarray Byte array containing the Intel integer to be unpacked.
   * @param pos      Offset that the Intel integer is stored within the byte array.
   * @return         Unpacked integer value.
   * @exception      java.lang.IndexOutOfBoundsException   If there is not enough data in the buffer.
   */
  public final static int getIntelInt(byte[] bytarray, int pos) throws java.lang.IndexOutOfBoundsException {

    //  Check if the byte array is long enough to restore the int

    if (bytarray.length < pos + 3)
      throw new java.lang.IndexOutOfBoundsException();

    //  Determine the byte ordering for this platform, and restore the int

    int iVal = 0;

    //  Restore the int value from the byte array

    int i1 = (int) bytarray[pos + 3] & 0xFF;
    int i2 = (int) bytarray[pos + 2] & 0xFF;
    int i3 = (int) bytarray[pos + 1] & 0xFF;
    int i4 = (int) bytarray[pos] & 0xFF;

    iVal = (i1 << 24) + (i2 << 16) + (i3 << 8) + i4;

    //  Return the int value

    return iVal;
  }

  /**
   * Unpack a 64-bit long.
   *
   * @param buf  Byte buffer containing the integer to be unpacked.
   * @param pos  Position within the buffer that the integer is stored.
   * @return     The unpacked 64-bit long value.
   * @exception  java.lang.IndexOutOfBoundsException   If there is not enough data in the buffer.
   */
  public final static long getLong(byte[] buf, int pos)
  	throws java.lang.IndexOutOfBoundsException {

    //  Check if the byte array is long enough to restore the long

    if (buf.length < pos + 7)
      throw new java.lang.IndexOutOfBoundsException();

    //  Restore the long value from the byte array

    long lVal = 0L;

		for ( int i = 0; i < 8; i++) {

			//	Get the current byte, shift the value and add to the return value

			long curVal = (long) buf[pos + i] & 0xFF;
			curVal = curVal << (( 7 - i) * 8);
			lVal += curVal;
		}

    //  Return the long value

    return lVal;
  }

  /**
   * Unpack a 64-bit integer that is stored in Intel format.
   *
   * @param bytarray Byte array containing the Intel long to be unpacked.
   * @param pos      Offset that the Intel integer is stored within the byte array.
   * @return         Unpacked long value.
   * @exception      java.lang.IndexOutOfBoundsException   If there is not enough data in the buffer.
   */
  public final static long getIntelLong(byte[] bytarray, int pos) throws java.lang.IndexOutOfBoundsException {

    //  Check if the byte array is long enough to restore the long

    if (bytarray.length < pos + 7)
      throw new java.lang.IndexOutOfBoundsException();

    //  Restore the long value from the byte array

    long lVal = 0L;

		for ( int i = 0; i < 8; i++) {

			//	Get the current byte, shift the value and add to the return value

			long curVal = (long) bytarray[pos + i] & 0xFF;
			curVal = curVal << (i * 8);
			lVal += curVal;
		}

    //  Return the long value

    return lVal;
  }

  /**
   * Unpack a 16-bit value that is stored in Intel format.
   *
   * @param bytarray Byte array containing the short value to be unpacked.
   * @param pos      Offset to start unpacking the short value.
   * @return         Unpacked short value.
   * @exception      java.lang.IndexOutOfBoundsException   If there is not enough data in the buffer.
   */
  public final static int getIntelShort(byte[] bytarray, int pos)
  	throws java.lang.IndexOutOfBoundsException {

    //  Check if the byte array is long enough to restore the int

    if (bytarray.length < pos)
      throw new java.lang.IndexOutOfBoundsException();

    //  Restore the short value from the byte array

    int sVal = (((int) bytarray[pos + 1] << 8) + ((int) bytarray[pos] & 0xFF));

    //  Return the short value

    return sVal & 0xFFFF;
  }

  /**
   * Unpack a 16-bit value.
   *
   * @param bytarray Byte array containing the short to be unpacked.
   * @param pos      Offset within the byte array that the short is stored.
   * @return         Unpacked short value.
   * @exception      java.lang.IndexOutOfBoundsException   If there is not enough data in the buffer.
   */
  public final static int getShort(byte[] bytarray, int pos) throws java.lang.IndexOutOfBoundsException {

    //  Check if the byte array is long enough to restore the int

    if (bytarray.length < pos)
      throw new java.lang.IndexOutOfBoundsException();

    //  Determine the byte ordering for this platform, and restore the short

    int sVal = 0;

    if (bigEndian == true) {

      //  Big endian

      sVal = ((((int) bytarray[pos + 1]) << 8) + ((int) bytarray[pos] & 0xFF));
    }
    else {

      //  Little endian

      sVal = ((((int) bytarray[pos]) << 8) + ((int) bytarray[pos + 1] & 0xFF));
    }

    //  Return the short value

    return sVal & 0xFFFF;
  }

  /**
   * Unpack a null terminated string from the data buffer.
   *
   * @param bytarray Byte array to unpack the string value from.
   * @param pos      Offset to start unpacking the string value.
   * @param maxlen   Maximum length of data to be searched for a null character.
   * @return         String, else null if the terminating null character was
   *                 not found.
   */
  public final static String getString(byte[] bytarray, int pos, int maxlen) {

    //  Search for the trailing null

    int maxpos = pos + maxlen;
    int endpos = pos;

    while (bytarray[endpos] != 0x00 && endpos < maxpos)
      endpos++;

    //  Check if we reached the end of the buffer

    if (endpos <= maxpos)
      return new String(bytarray, pos, endpos - pos);
    return null;
  }

  /**
   * Unpack a null terminated string from the data buffer. The string may be ASCII or Unicode.
   *
   * @param bytarray Byte array to unpack the string value from.
   * @param pos      Offset to start unpacking the string value.
   * @param maxlen   Maximum length of data to be searched for a null character.
   * @param isUni		 Unicode string if true, else ASCII string
   * @return         String, else null if the terminating null character was
   *                 not found.
   */
  public final static String getString(byte[] bytarray, int pos, int maxlen, boolean isUni) {

		//	Get a string from the buffer

		String str = null;

		if ( isUni)
			str = getUnicodeString(bytarray,pos,maxlen);
		else
			str = getString(bytarray,pos,maxlen);

		//	return the string

		return str;
  }

  /**
   * Unpack a null terminated Unicode string from the data buffer.
   *
   * @param byt       Byte array to unpack the string value from.
   * @param pos      Offset to start unpacking the string value.
   * @param maxlen   Maximum length of data to be searched for a null character.
   * @return         String, else null if the terminating null character was
   *                 not found.
   */
  public final static String getUnicodeString(byte[] byt, int pos, int maxlen) {

		//	Check for an empty string

		if ( maxlen == 0)
			return "";

    //  Search for the trailing null

    int maxpos = pos + (maxlen * 2);
    int endpos = pos;
    char[] chars = new char[maxlen];
    int cpos = 0;
    char curChar;

    do {

      //  Get a Unicode character from the buffer

      curChar = (char) (((byt[endpos + 1] & 0xFF) << 8) + ( byt[endpos] & 0xFF));

      //  Add the character to the array

      chars[cpos++] = curChar;

      //  Update the buffer pointer

      endpos += 2;

    } while (curChar != 0 && endpos < maxpos);

    //  Check if we reached the end of the buffer

    if (endpos <= maxpos) {
    	if ( curChar == 0)
    		cpos--;
      return new String(chars, 0, cpos);
    }
    return null;
  }

  /**
   * Pack a 32-bit integer into the supplied byte buffer.
   *
   * @param val      Integer value to be packed.
   * @param bytarray Byte buffer to pack the integer value into.
   * @param pos      Offset to start packing the integer value.
   * @exception      java.lang.IndexOutOfBoundsException   If the buffer does not have enough space.
   */
  public final static void putInt(int val, byte[] bytarray, int pos) throws java.lang.IndexOutOfBoundsException {

    //  Check if the byte array is long enough to store the int

    if (bytarray.length < pos + 3)
      throw new java.lang.IndexOutOfBoundsException();

    //  Pack the integer value

    bytarray[pos]     = (byte) ((val >> 24) & 0xFF);
    bytarray[pos + 1] = (byte) ((val >> 16) & 0xFF);
    bytarray[pos + 2] = (byte) ((val >> 8) & 0xFF);
    bytarray[pos + 3] = (byte) (val & 0xFF);
  }

  /**
   * Pack an 32-bit integer value in Intel format.
   *
   * @param val      Integer value to be packed.
   * @param bytarray Byte array to pack the value into.
   * @param pos      Offset to start packing the integer value.
   * @exception      java.lang.IndexOutOfBoundsException   If there is not enough data in the buffer.
   */
  public final static void putIntelInt(int val, byte[] bytarray, int pos) throws java.lang.IndexOutOfBoundsException {

    //  Check if the byte array is long enough to store the int

    if (bytarray.length < pos + 3)
      throw new java.lang.IndexOutOfBoundsException();

    //  Store the int value in the byte array

    bytarray[pos + 3] = (byte) ((val >> 24) & 0xFF);
    bytarray[pos + 2] = (byte) ((val >> 16) & 0xFF);
    bytarray[pos + 1] = (byte) ((val >> 8) & 0xFF);
    bytarray[pos] = (byte) (val & 0xFF);
  }

	/**
	 * Pack a 64-bit integer value into the buffer
	 *
	 * @param val      Integer value to be packed.
	 * @param bytarray Byte array to pack the value into.
	 * @param pos      Offset to start packing the integer value.
	 * @exception      java.lang.IndexOutOfBoundsException   If there is not enough data in the buffer.
	 */
	public final static void putLong(long val, byte[] bytarray, int pos)
		throws java.lang.IndexOutOfBoundsException {

		//  Check if the byte array is long enough to store the int

		if (bytarray.length < pos + 7)
			throw new java.lang.IndexOutOfBoundsException();

		//  Store the long value in the byte array

		bytarray[pos]     = (byte) ((val >> 56) & 0xFF);
		bytarray[pos + 1] = (byte) ((val >> 48) & 0xFF);
		bytarray[pos + 2] = (byte) ((val >> 40) & 0xFF);
		bytarray[pos + 3] = (byte) ((val >> 32) & 0xFF);
		bytarray[pos + 4] = (byte) ((val >> 24) & 0xFF);
		bytarray[pos + 5] = (byte) ((val >> 16) & 0xFF);
		bytarray[pos + 6] = (byte) ((val >> 8) & 0xFF);
		bytarray[pos + 7] = (byte) (val & 0xFF);
	}


  /**
   * Pack a 64-bit integer value in Intel format.
   *
   * @param val      Integer value to be packed.
   * @param bytarray Byte array to pack the value into.
   * @param pos      Offset to start packing the integer value.
   * @exception      java.lang.IndexOutOfBoundsException   If there is not enough data in the buffer.
   */
  public final static void putIntelLong(long val, byte[] bytarray, int pos)
  	throws java.lang.IndexOutOfBoundsException {

    //  Check if the byte array is long enough to store the int

    if (bytarray.length < pos + 7)
      throw new java.lang.IndexOutOfBoundsException();

    //  Store the long value in the byte array

    bytarray[pos + 7] = (byte) ((val >> 56) & 0xFF);
    bytarray[pos + 6] = (byte) ((val >> 48) & 0xFF);
    bytarray[pos + 5] = (byte) ((val >> 40) & 0xFF);
    bytarray[pos + 4] = (byte) ((val >> 32) & 0xFF);
    bytarray[pos + 3] = (byte) ((val >> 24) & 0xFF);
    bytarray[pos + 2] = (byte) ((val >> 16) & 0xFF);
    bytarray[pos + 1] = (byte) ((val >> 8) & 0xFF);
    bytarray[pos] = (byte) (val & 0xFF);
  }

  /**
   * Pack a 64-bit integer value in Intel format.
   *
   * @param val      Integer value to be packed.
   * @param bytarray Byte array to pack the value into.
   * @param pos      Offset to start packing the integer value.
   * @exception      java.lang.IndexOutOfBoundsException   If there is not enough data in the buffer.
   */
  public final static void putIntelLong(int val, byte[] bytarray, int pos)
  	throws java.lang.IndexOutOfBoundsException {

    //  Check if the byte array is long enough to store the int

    if (bytarray.length < pos + 7)
      throw new java.lang.IndexOutOfBoundsException();

    //  Store the int value in the byte array

    bytarray[pos + 7] = (byte) 0;
    bytarray[pos + 6] = (byte) 0;
    bytarray[pos + 5] = (byte) 0;
    bytarray[pos + 4] = (byte) 0;
    bytarray[pos + 3] = (byte) ((val >> 24) & 0xFF);
    bytarray[pos + 2] = (byte) ((val >> 16) & 0xFF);
    bytarray[pos + 1] = (byte) ((val >> 8) & 0xFF);
    bytarray[pos] = (byte) (val & 0xFF);
  }

  /**
   * Pack a 16 bit value in Intel byte order.
   *
   * @param val      Short value to be packed.
   * @param bytarray Byte array to pack the short value into.
   * @param pos      Offset to start packing the short value.
   * @exception      java.lang.IndexOutOfBoundsException   If there is not enough data in the buffer.
   */
  public final static void putIntelShort(int val, byte[] bytarray, int pos) throws java.lang.IndexOutOfBoundsException {

    //  Check if the byte array is long enough to store the short

    if (bytarray.length < pos)
      throw new java.lang.IndexOutOfBoundsException();

    // 	Pack the short value

    bytarray[pos + 1] = (byte) ((val >> 8) & 0xFF);
    bytarray[pos]     = (byte) (val & 0xFF);
  }

  /**
   * Pack a 16-bit value into the supplied byte buffer.
   *
   * @param val      Short value to be packed.
   * @param bytarray Byte array to pack the short value into.
   * @param pos      Offset to start packing the short value.
   * @exception      java.lang.IndexOutOfBoundsException   If there is not enough data in the buffer.
   */
  public final static void putShort(int val, byte[] bytarray, int pos) throws java.lang.IndexOutOfBoundsException {

    //  Check if the byte array is long enough to store the short

    if (bytarray.length < pos)
      throw new java.lang.IndexOutOfBoundsException();

    //  Pack the short value

    bytarray[pos]     = (byte) ((val >> 8) & 0xFF);
    bytarray[pos + 1] = (byte) (val & 0xFF);
  }

  /**
   * Pack a string into a data buffer
   *
   * @param str        String to be packed into the buffer
   * @param bytarray   Byte array to pack the string into
   * @param pos        Position to start packing the string
   * @param nullterm   true if the string should be null terminated, else false
   * @return           The ending buffer position
   */
  public final static int putString(String str, byte[] bytarray, int pos, boolean nullterm) {

    //  Get the string as a byte array

    byte[] byts = str.getBytes();

    //  Pack the data bytes

    int bufpos = pos;

    for (int i = 0; i < byts.length; i++)
      bytarray[bufpos++] = byts[i];

    //  Null terminate the string, if required

    if (nullterm == true)
      bytarray[bufpos++] = 0;

    //  Return the next free buffer position

    return bufpos;
  }

  /**
   * Pack a string into a data buffer
   *
   * @param str        String to be packed into the buffer
   * @param fldLen      Field length, will be space padded if short
   * @param bytarray   Byte array to pack the string into
   * @param pos        Position to start packing the string
   * @return           The ending buffer position
   */
  public final static int putString(String str, int fldLen, byte[] bytarray, int pos) {

    //  Get the string as a byte array

    byte[] byts = str.getBytes();

    //  Pack the data bytes

    int bufpos = pos;
    int idx = 0;

    while (idx < fldLen) {
      if (idx < byts.length)
        bytarray[bufpos++] = byts[idx];
      else
        bytarray[bufpos++] = (byte) 0;
      idx++;
    }

    //  Return the next free buffer position

    return bufpos;
  }

  /**
   * Pack a string into a data buffer. The string may be ASCII or Unicode.
   *
   * @param str        String to be packed into the buffer
   * @param bytarray   Byte array to pack the string into
   * @param pos        Position to start packing the string
   * @param nullterm   true if the string should be null terminated, else false
   * @param isUni 		 true if the string should be packed as Unicode, false to pack as ASCII
   * @return           The ending buffer position
   */
  public final static int putString(String str, byte[] bytarray, int pos, boolean nullterm, boolean isUni) {

		//	Pack the string

		int newpos = -1;

		if ( isUni)
			newpos = putUnicodeString(str,bytarray,pos,nullterm);
		else
			newpos = putString(str,bytarray,pos,nullterm);

		//	Return the end of string buffer position

		return newpos;
  }

  /**
   * Pack a Unicode string into a data buffer
   *
   * @param str        String to be packed into the buffer
   * @param bytarray   Byte array to pack the string into
   * @param pos        Position to start packing the string
   * @param nullterm   true if the string should be null terminated, else false
   * @return           The ending buffer position
   */
  public final static int putUnicodeString(String str, byte[] bytarray, int pos, boolean nullterm) {

    //  Pack the data bytes

    int bufpos = pos;

    for (int i = 0; i < str.length(); i++) {

      //	Get the current character from the string

      char ch = str.charAt(i);

      //	Pack the unicode character

      bytarray[bufpos++] = (byte) (ch & 0xFF);
      bytarray[bufpos++] = (byte) ((ch & 0xFF00) >> 8);
    }

    //  Null terminate the string, if required

    if (nullterm == true) {
      bytarray[bufpos++] = 0;
      bytarray[bufpos++] = 0;
    }

    //  Return the next free buffer position

    return bufpos;
  }

  /**
   * Pack nulls into the buffer.
   *
   * @param buf   Buffer to pack data into.
   * @param pos   Position to start packing.
   * @param cnt   Number of nulls to pack.
   * @exception java.lang.ArrayIndexOutOfBoundsException  If the buffer does not have enough space.
   */
  public final static void putZeros(byte[] buf, int pos, int cnt) throws java.lang.ArrayIndexOutOfBoundsException {

    //  Check if the buffer is big enough

    if (buf.length < (pos + cnt))
      throw new java.lang.ArrayIndexOutOfBoundsException();

    //  Pack the nulls

    for (int i = 0; i < cnt; i++)
      buf[pos + i] = 0;
  }

	/**
	 * Align a buffer offset on a word boundary
	 *
	 * @param pos int
	 * @return int
	 */
	public final static int wordAlign(int pos) {
	  return ( pos + 1) & 0xFFFFFFFE;
	}

	/**
	 * Align a buffer offset on a longword boundary
	 *
	 * @param pos int
	 * @return int
	 */
	public final static int longwordAlign(int pos) {
	  return ( pos + 3) & 0xFFFFFFFC;
	}

	/**
	 * Calculate the string length in bytes
	 *
	 * @param str String
	 * @param uni boolean
	 * @param nul boolean
	 * @return int
	 */
	public final static int getStringLength(String str, boolean uni, boolean nul) {

	  //	Calculate the string length in bytes

		int len = str.length();
		if ( nul)
			len += 1;
		if ( uni)
			len *= 2;

		return len;
	}

	/**
	 * Calculate the new buffer position after the specified string and encoding (ASCII or Unicode)
	 *
	 * @param pos int
	 * @param str String
	 * @param uni boolean
	 * @param nul boolean
	 * @return int
	 */
	public final static int getBufferPosition(int pos, String str, boolean uni, boolean nul) {

		//	Calculate the new buffer position

		int len = str.length();
		if ( nul)
			len += 1;
		if ( uni)
			len *= 2;

		return pos + len;
	}
}
